(************************************************************************)(*	an example programme for a Photoshop acquisition plug-in 	*)(*			  by Daniel W. Rickey				*)(*			London, Ontario, CANADA				*)(*			drickey@irus.rri.uwo.ca				*)(*			written in Think Pascal				*)(*		based on an MPW example programme by Thomas Knoll	*)(*			last modified 1994-04-27			*)(*									*)(*	Note that this Plug-in assumes that System 7.0 or newer is used	*)(************************************************************************)Unit ExampleAcquisition;Interface	Uses		AcquireInterface;  {When a plug-in module is called, Adobe Photoshop opens the resource fork}  {of the plug-in file, loads the resource into to memory and locks it.}  {The plug-in is then called starting at the first byte of the resource with the}  {following calling convention:}	Procedure Main (selector: Integer; AcquireRecord: AcquireRecordPtr; Var myData: Handle; Var result: Integer);Implementation	Const		kAbortAcquisition = 1;	{an error occurred or the user selected cancel}    (* the following are key codes to be used in dialogue box filters *)		kEnterKey = $03;		kBackSpace = $08;		kTabKey = $09;		kReturnKey = $0D;		kEscKey = $1B;		kLeftArrow = $1C;		kRightArrow = $1D;		kUpArrow = $1E;		kDownArrow = $1F;		kDeleteKey = $7F;    (* these are friendly keys and are passed through the filters *)		kControlKeys = [kBackSpace, kTabKey, kLeftArrow, kRightArrow, kUpArrow, kDownArrow, kDeleteKey];	Type    {keep all of our variables in one record handle}    {this will be used instead of keeping global variables}		myDataType = Record				Acquire: AcquireRecordPtr;				Result: Integer;				LastRows: Integer;	{keep track of the image size and type}				LastCols: Integer;	{If the plug-in is called more than once, the last}				LastMode: Integer;	{set of parameters will be presented to the user.}				ImageRow: Integer;	{Image row being acquired; 1..Number of rows}				ImagePlane: Integer;	{image plane being acquired; 0..3}			End;		myDataPtr = ^myDataType;		myDataHand = ^myDataPtr;  (**** Calls the host's TestAbort function ****)  (**** may call this several times a second to allow the user to abort the acquisition ****)  (**** this also changes the cursor to a watch and periodically moves the watch hands ****)	Function TestAbort (myData: myDataHand): Boolean;		Var			address: ProcPtr;		Function DoTestAbort (codeAddress: ProcPtr): Boolean;		Inline			$205F, $4E90;	Begin	TestAbort := FALSE;	Exit(TestAbort);	address := myData^^.Acquire^.abortProc;	TestAbort := DoTestAbort(address);	End;  {TestAbort}  (**** Calls the host's UpdateProgress procedure ****)  (**** The first parameter is the number of operations completed ****)  (**** the second is the total number of operations to do ****)	Procedure UpdateProgress (NumberDone, total: LongInt; myData: myDataHand);		Var			address: ProcPtr;		Procedure DoUpdateProgress (NumberDone, total: LongInt; CodeAddress: ProcPtr);		Inline			$205F, $4E90;	Begin	address := myData^^.Acquire^.ProgressProc;	DoUpdateProgress(NumberDone, total, address);	End;  {UpdateProgress}  (**** Displays the about dialogue box for the plug-in module. ****)	Procedure DoAbout;		Const			kDialogueID = 16000;		Var			theDialogueHand: DialogTHndl;			theDialogue: DialogPtr;			theItem: Integer;	Begin  {load the dialgue resource into memory and lock it}	theDialogueHand := DialogTHndl(GetResource('DLOG', kDialogueID));	HNoPurge(Handle(theDialogueHand));  {get our about dialogue from the resource}	theDialogue := GetNewDialog(kDialogueID, Nil, WindowPtr(-1));  {display the dialogue and wait for the user to select okay}	ModalDialog(Nil, theItem);  {get rid of the dialogue window and the resource}	DisposDialog(theDialogue);	HPurge(Handle(theDialogueHand))	End;  {DoAbout}  (**** Prepare to acquire an image.  If the plug-in module needs only a limited ****)  (****  amount of memory, it can lower the value of the 'maxData' field. ****)	Procedure DoPrepare (myData: myDataHand);	Begin  {Photoshop initialises maxData to the maximum of number of bytes it can free}  {divide by two to give half to photoshop and half to the plug-in}	myData^^.Acquire^.maxData := myData^^.Acquire^.maxData Div 2;	End;  {DoPrepare}  (**** outlines the OK button in a dialogue box ****)  (**** this is from the new inside mac, Toolbox essentials ****)	Procedure OutlineOKbutton (theDialogue: DialogPtr; TheItem: Integer);		Const			kOkayButton = 1;			kButtonFrameInset = -4;			kButtonFrameSize = 3;		Var			itemType: Integer;			itemRect: Rect;			ItemHandle: Handle;			CurrentPen: PenState;			ButtonOval: Integer;			OldPort: WindowPtr;			thePattern: Pattern;	Begin	GetDItem(theDialogue, kOkayButton, itemType, ItemHandle, itemRect);	GetPort(OldPort);	SetPort(ControlHandle(itemHandle)^^.ContrlOwner);	GetPenState(CurrentPen);	PenNormal;	InsetRect(itemRect, kButtonFrameInset, kButtonFrameInset);	FrameRoundRect(itemRect, 16, 16);	ButtonOval := (ItemRect.Bottom - ItemRect.Top) Div 2 + 2;  {get the black pen pattern from the system resource}	GetIndPattern(thePattern, 0, 1);  {set the pen pattern to black; can not use "Black" since it is an A5 QD global}	PenPat(thePattern);	PenSize(kButtonFrameSize, kButtonFrameSize);	FrameRoundRect(itemRect, ButtonOval, ButtonOval);	SetPenState(CurrentPen);	SetPort(OldPort);	End;  {OutlineOKbutton}  (**** filter key strokes function for the image parameters dialogue window ****)  (**** allows only numbers to be entered in the image width and height text boxes ****)	Function myImageParametersFilter (theDialogue: DialogPtr; Var TheEvent: EventRecord; Var ItemHit: Integer): Boolean;		Const			kDialogueID = 16001;			kOkayItem = 1;			kCancelItem = 2;			kImageWidthItem = 3;			kImageHeightItem = 4;			kBitMapItem = 5;			kGreyScaleItem = 6;			kColourItem = 7;			kRGBcolourItem = 8;			kOkayOutlineItem = 13;			DefaultItem = kOkayItem;		Var			Key: Integer;			TextField: Integer;	Begin	myImageParametersFilter := FALSE;  (* check if a key was pressed, or an autoRepeat happened *)	If (TheEvent.What = KeyDown) Or (TheEvent.What = AutoKey) Then		Begin    (* check what text field the keystokes are occuring in *)		TextField := DialogPeek(TheDialogue)^.EditField + 1;    {get the key that was pressed}		Key := band(TheEvent.Message, CharCodeMask);    {if the key was a control key, or in the range 0 to 9, then do not filter it}		If (Key In kControlKeys) Or (Key In [Ord('0')..Ord('9')]) Then			Begin			myImageParametersFilter := FALSE;			End    {if the enter or return key was pressed, then pass back okay button selected}		Else If Key In [kEnterKey, kReturnKey] Then			Begin			myImageParametersFilter := TRUE;			ItemHit := DefaultItem;			End    {if we are in a text field and a non-numeric key is pressed then filter}		Else If (TextField In [kImageWidthItem, kImageHeightItem]) And Not (Key In [Ord('0')..Ord('9')]) Then			Begin			myImageParametersFilter := TRUE;			End;		End;  {If KeyDown}	End;	{myImageParametersFilter}  (**** Converts a string to a number between 1 and 30000 ****)  (****  Returns false if not a valid number or out of range ****)	Function ConvertString (theString: Str255; Var theNumber: Integer): Boolean;		Var			Loop: Integer;			x: LongInt;	Begin	ConvertString := FALSE;  {loop through all of the characters in the string and make sure all are numbers}  {we do not really need to do this as the dialogue filter should have filtered}  {all non-numeric characters}	For Loop := 1 To Length(theString) Do		Begin		If Not (theString[Loop] In ['0'..'9']) Then			Begin			EXIT(ConvertString);			End;		End;  {convert the string to a long integer}	StringToNum(theString, x);  {make sure the number is reasonable }	If (x > 0) And (x < 30001) Then		Begin		ConvertString := TRUE;		theNumber := x;		End;	End;  {ConvertString}  (**** Prompt the user for the image parameters; Returns false if the user cancels ****)	Function GetImageParameters (Var ImageWidth, ImageHeight, mode: Integer): BOOLEAN;		Const			kDialogueID = 16001;			kOkayItem = 1;			kCancelItem = 2;			kImageWidthItem = 3;			kImageHeightItem = 4;			kBitMapItem = 5;			kGreyScaleItem = 6;			kColourItem = 7;			kRGBcolourItem = 8;			kOkayOutlineItem = 13;		Var			itemRect: Rect;			WidthString, HeightString: Str255;			ItemHandle: Handle;			TheDialogue: DialogPtr;			TheDialogueHand: DialogTHndl;			TheItem: Integer;			itemType: Integer;			WidthText: Handle;			HeightText: Handle;			bButton: ControlHandle;			gButton: ControlHandle;			iButton: ControlHandle;			cButton: ControlHandle;			Done: Boolean;	Begin	Done := FALSE;  {load our dialogue resource into memory to make sure we do not}  {use another plug-in's dialogue}	TheDialogueHand := DialogTHndl(GetResource('DLOG', kDialogueID));	HNoPurge(Handle(TheDialogueHand));  {get our dialogue from the dialogue resource}	TheDialogue := GetNewDialog(kDialogueID, Nil, WindowPtr(-1));  {get a handle to the "user" button outline item}	GetDItem(TheDialogue, kOkayOutlineItem, itemType, ItemHandle, itemRect);  {install the drawing routine for the application defined button outline}	SetDItem(TheDialogue, kOkayOutlineItem, itemType, Handle(@OutlineOKbutton), itemRect);  {set the image height value in the text edit box}	GetDItem(TheDialogue, kImageHeightItem, itemType, HeightText, itemRect);	NumToString(ImageHeight, HeightString);	SetIText(HeightText, HeightString);  {set the image width value in the text edit box}	GetDItem(TheDialogue, kImageWidthItem, itemType, WidthText, itemRect);	NumToString(ImageWidth, WidthString);	SetIText(WidthText, WidthString);  {select all of the text in the Image width text edit box}	SelIText(TheDialogue, kImageWidthItem, 0, 32767);	Repeat    {set the states of the four image mode radio-buttons}		GetDItem(TheDialogue, kBitMapItem, itemType, Handle(bButton), itemRect);		SetCtlValue(bButton, ORD(mode = acquireModeBitmap));		GetDItem(TheDialogue, kGreyScaleItem, itemType, Handle(gButton), itemRect);		SetCtlValue(gButton, ORD(mode = acquireModeGrayScale));		GetDItem(TheDialogue, kColourItem, itemType, Handle(iButton), itemRect);		SetCtlValue(iButton, ORD(mode = acquireModeIndexedColor));		GetDItem(TheDialogue, kRGBcolourItem, itemType, Handle(cButton), itemRect);		SetCtlValue(cButton, ORD(mode = acquireModeRGBColor));    {wait for the user to select something}		ModalDialog(@myImageParametersFilter, theItem);		Case theItem Of		kBitMapItem: 			Begin			mode := acquireModeBitmap;			End;		kGreyScaleItem: 			Begin			mode := acquireModeGrayScale;			End;		kColourItem: 			Begin			mode := acquireModeIndexedColor;			End;		kRGBcolourItem: 			Begin			mode := acquireModeRGBColor;			End;		kOkayItem: {user selected okay button or pressed the return key}			Begin      {get both the image width and image height strings from the text edit boxes}			GetIText(WidthText, WidthString);			GetIText(HeightText, HeightString);      {convert the width and height strings to numbers}      {if an error then beep & select the offending text}			If Not ConvertString(WidthString, ImageWidth) Then				Begin				SelIText(TheDialogue, kImageWidthItem, 0, 32767);				SysBeep(1);				End			Else If Not ConvertString(HeightString, ImageHeight) Then				Begin				SelIText(TheDialogue, kImageHeightItem, 0, 32767);				SysBeep(1);				End			Else				Begin        {if there were no errors, then return true and exit the loop}				GetImageParameters := TRUE;				Done := TRUE;				End;			End;		kCancelItem: 			Begin      {user selected cancel, so exit and return a false value}			GetImageParameters := FALSE;			Done := TRUE;			End;		Otherwise			Begin			End;		End;  {Case theItem}	Until Done;  {get rid of our dialogue window}	DisposDialog(TheDialogue);  {remove our dialogue resource from memory}	HPurge(Handle(TheDialogueHand));	End;  {GetImageParameters}(**** Asks the user and the returns the image parmaters to the calling program ****)(**** This call lets Photoshop know the mode, size and resolution of the image ****)(**** being returned, so Photoshop can allocate and initialise its data structures ****)(* The plug-in will display the dialogue box during this call and set the ****)(**** imageMode, imageSize,depth, planes, imageHRes and imageVRes fields. ****)(**** For an indexed color image, the redLUT , greenLUT and blueLUT fields are set ****)(**** If a duotone mode image is being acquired, the duotoneInfo field is set ****)	Procedure DoStart (myData: myDataHand);		Var			j: Integer;	Begin	With myData^^, myData^^.Acquire^ Do		Begin		imageSize.v := LastRows;		imageSize.h := LastCols;		imageMode := LastMode;    (* get the image size and mode from the user; exit if canceled *)		If Not GetImageParameters(imageSize.v, imageSize.h, imageMode) Then			Begin			Result := kAbortAcquisition;			Exit(DoStart)			End;    (* keep track of the user's choices for next time this is called *)		LastRows := imageSize.v;		LastCols := imageSize.h;		LastMode := imageMode;    (* start with image plane 0; = red for RGB images *)		ImagePlane := 0;    (* start with the first row of the image plane *)		ImageRow := 1;    (* initialise the host's image parameter record with appropriate values *)		Case ImageMode Of		acquireModeBitmap: 			Begin			Depth := 1;			(* 1 Bit per sample/pixel *)			Planes := 1;			(* 1 sample per pixel *)			imageHRes := FixRatio(300, 1);	(* 300 Horizontal Pixels per inch *)			imageVRes := FixRatio(300, 1);	(* 300 Vertical Pixels per inch *)			End;		acquireModeGrayScale: 			Begin			Depth := 8;			(* 8 Bits per sample/pixel *)			Planes := 1;			(* 1 sample per pixel *)			imageHRes := FixRatio(72, 1);	(* 72 Horizontal Pixels per inch *)			imageVRes := FixRatio(72, 1);	(* 72 Vertical Pixels per inch *)			End;		acquireModeIndexedColor: 			Begin			Depth := 8;			Planes := 1;			imageHRes := FixRatio(72, 1);			imageVRes := FixRatio(72, 1);      {initialise the Look-Up-Tables that are used for indexed colours only}      {the following is an arbitrary colour LUT}			For j := 0 To 255 Do				Begin				redLUT[j] := CHR(j);				greenLUT[j] := CHR(255 - j);				blueLUT[j] := CHR(j Div 2)				End;			End;		acquireModeRGBColor: 			Begin			Depth := 8;			Planes := 3;			imageHRes := FixRatio(72, 1);			imageVRes := FixRatio(72, 1);			End;		acquireModeCMYKColor: 			Begin			End;		acquireModeHSLColor: 			Begin			End;		acquireModeHSBColor: 			Begin			End;		acquireModeMultichannel: 			Begin			End;		acquireModeDuotone: 			Begin			End;		Otherwise			Begin			Result := kAbortAcquisition;			End;		End;  {Case ImageMode}		End;  {With}	End;  {DoStart}  (**** This call returns a strip of the image to Photoshop. ****)  (**** Photoshop will continue to call this routine until an error is returned ****)  (**** or the data field is set to Nil.*)	Procedure DoContinue (myData: myDataHand);		Var			TheStripPtr: Ptr;			aRow: LongInt;			aColumn: LongInt;			RowsInStrip: LongInt;	Begin	With myData^^, myData^^.Acquire^ Do		Begin    {if any data is left from the previous call, dispose of it}		If data <> Nil Then			Begin			DisposPtr(data);			data := Nil			End;    (* the plug-in returns the image data in the "data" field of the acquisition record *)    (* the image can be returned in many strips; maximum strip size is set by "maxData" *)    (* The area of the image strip being returned is specified by theRect and by the loPlane and hiPlane fields. *)    (* the byte spacing between columns; we will send each image plane separately *)		colBytes := 1;    (* this is the number of bytes in one image row, i.e., the byte spacing between rows *)		If depth = 8 Then			Begin			rowBytes := imageSize.h			End		Else			Begin			rowBytes := BSR(imageSize.h + 7, 3);			End;    (* determine the maximum number of image rows that can fit into a strip *)		RowsInStrip := maxData Div rowBytes;    {make sure the size of a single row is not larger than the biggest strip buffer}		If RowsInStrip < 1 Then			Begin			Result := memFullErr;			EXIT(DoContinue)			End;    (* acquire/generate image data for all image planes *)		If ImagePlane < planes Then			Begin      {tell the host what image plane is being returned; RGB images are one colour per plane}			loPlane := ImagePlane;			hiPlane := ImagePlane;      (* make sure that the rows in the strip is not larger than the remaining image *)			If RowsInStrip > (imageSize.v - ImageRow + 1) Then				Begin				RowsInStrip := imageSize.v - ImageRow + 1;				End;      (* allocate memory for the image strip *)			data := NewPtrClear(RowsInStrip * rowBytes);      (* check if the memory was allocated properly *)			If data = Nil Then				Begin				Result := memFullErr;				Exit(DoContinue)				End;      (* position and size of the image strip; Photoshop starts at zero thus the "-1" *)			SetRect(theRect, 0, ImageRow - 1, imageSize.h, ImageRow + RowsInStrip - 1);      (* have our pointer point to the start of the image strip memory *)			TheStripPtr := data;      (* loop through all of the rows in the image strip *)			For aRow := ImageRow To ImageRow + RowsInStrip - 1 Do				Begin        (* check if the user has pressed command-period; exit if true *)				If TestAbort(myData) Then					Begin					Result := kAbortAcquisition;					Exit(DoContinue)					End;        {tell the host to update its progress bar}				UpdateProgress(ImagePlane * ORD4(imageSize.v) + aRow, planes * ORD4(imageSize.v), myData);        (* This is where values are assigned to the pixels in the image. *)        (* One would normally read in an image from disc or a scanner or just*)	(* generate a pretty picture using fractals or what-ever. *)        (* Here we just generate a pretty picture. *)        {$PUSH}        {$R-}				Case ImageMode Of        {for a bitmapped image, set the values of 8 pixels at once}				acquireModeBitmap: 					Begin          (* loop through all bytes in one row of the image strip *)					For aColumn := 0 To rowBytes - 1 Do						Begin						TheStripPtr^ := BSL($FF, BAND(aRow, 7));            (* point to the next byte in the image strip *)						TheStripPtr := Ptr(ORD4(TheStripPtr) + 1);						End;  {For aColumn}					End;  {acquireModeBitmap}        {place one byte in each pixel; only have one image plane to worry about}				acquireModeGrayScale: 					Begin          (* loop through all bytes/pixels in one row of the image strip *)					For aColumn := 0 To rowBytes - 1 Do						Begin						TheStripPtr^ := BAND(aColumn + aRow, $FF);            (* point to the next byte in the image strip *)						TheStripPtr := Ptr(ORD4(TheStripPtr) + 1);						End;  {For aColumn}					End;  {acquireModeGrayScale}        {place one byte in each pixel; only have one image plane to worry about}				acquireModeIndexedColor: 					Begin          (* loop through all bytes/pixels in one row of the image strip *)					For aColumn := 0 To rowBytes - 1 Do						Begin						TheStripPtr^ := BAND(aRow + aColumn, $FF);            (* point to the next byte in the image strip *)						TheStripPtr := Ptr(ORD4(TheStripPtr) + 1);						End;  {For aColumn}					End;  {acquireModeIndexedColor}        {for an RGB image, place different values in each of the 3 image planes}				acquireModeRGBColor: 					Begin          (* loop through all bytes/pixels in one row of the image strip *)					For aColumn := 0 To rowBytes - 1 Do						Begin						Case ImagePlane Of						0: 							TheStripPtr^ := BAND(aColumn, $FF);						1: 							TheStripPtr^ := BAND(aRow - 1, $FF);						2: 							TheStripPtr^ := BAND(aRow - 1 + aColumn, $FF);						Otherwise							Begin							End;						End;  {Case ImagePlane}            (* point to the next byte in the image strip *)						TheStripPtr := Ptr(ORD4(TheStripPtr) + 1);						End;  {For aColumn}					End;  {acquireModeRGBColor}				Otherwise					Begin					Result := kAbortAcquisition;					End;				End;  {Case ImageMode}        {$POP}				End;  {For aRow}      {move to the start of the next image strip}			ImageRow := ImageRow + RowsInStrip;      {if we have reached the end of an image plane then move onto the next image plane}			If ImageRow > imageSize.v Then				Begin				ImageRow := 1;				ImagePlane := ImagePlane + 1				End;			End;  {If ImagePlane}		End;  {With}	End;  {DoContinue}(**** This routine will always be called if DoStart does not return an error ****)(**** even if DoContinue returns an error or the user aborts the operation ****)(****  This allows the Plug-In to delete the image strip buffer ****)	Procedure DoFinish (Var myData: myDataHand);	Begin  {if the image strip data buffer is not empty, then delete it}	If myData <> Nil Then		Begin		With myData^^.Acquire^ Do			If data <> Nil Then				Begin				DisposPtr(data);				data := Nil				End;		End;	End;  {DoFinish}  (**** Main dispatching routine.  Initialises and sets up the record variables,****)  (**** and performs the operation specified by the host's selector. ****)  (**** Think pascal wants this to be called "Main" to be placed first in the resource. ****)  (**** other development systems may require that this be the first routine in the source code ****)	Procedure Main (selector: Integer; AcquireRecord: AcquireRecordPtr; Var myData: Handle; Var result: Integer);	Begin	result := NoErr;  { See if this is the first time called }  {If so, allocate and initialise the record handle for our variables}	If (myData = Nil) Then		Begin    {allocate memory for our record}		myData := NewHandleClear(SizeOf(myDataType));    {check if the memory allocation was successful}		If myData = Nil Then			Begin			result := MemFullErr;			Exit(Main);			End;    { Initialize our plug-in values }		myDataHand(myData)^^.LastRows := 256;		myDataHand(myData)^^.LastCols := 256;		myDataHand(myData)^^.LastMode := acquireModeRGBColor		End;  {lock the handle just to be certain}	HLock(myData);  {keep track of the pointer to the AcquireRecord}	myDataHand(myData)^^.Acquire := AcquireRecord;  {no error - yet}	myDataHand(myData)^^.Result := noErr;  {perform the requested action}	Case selector Of	acquireSelectorAbout: {show the about dialogue}		DoAbout;	acquireSelectorPrepare: {set things up for acquisition}		DoPrepare(myDataHand(myData));	acquireSelectorStart: {get parameters from the user}		DoStart(myDataHand(myData));	acquireSelectorContinue: {acquire or produce the image}		DoContinue(myDataHand(myData));	acquireSelectorFinish: {tidy things up before modual is disposed of}		DoFinish(myDataHand(myData));	Otherwise		myDataHand(myData)^^.Result := acquireBadParameters	End;  {Case selector}  {return the result of the request}	result := myDataHand(myData)^^.Result;  {unlock the handle before returning to the host programme}	HunLock(myData);	End;  {Main}End.